feat(dev-api): add /v1/dev/user/ask and the "omi ask" CLI command#10314
feat(dev-api): add /v1/dev/user/ask and the "omi ask" CLI command#10314aryanorastar wants to merge 2 commits into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
72052a4 to
437e64d
Compare
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks for putting this together — the feature is coherent with the Developer API/CLI direction, and I like that it reuses the existing conversation search + cited RAG path rather than inventing a second retrieval stack.
I do think this needs changes before maintainer sign-off:
POST /v1/dev/user/askis a new public Developer API endpoint that can invoke an LLM on demand, but the route policy explicitly declaresrate_limit: noneand the implementation does not add a quota/cost guard. Even withconversations:readauth, a leaked or overused developer key could turn this into an unbounded billable endpoint over sensitive user conversation data. Please put this behind an appropriate per-user/key rate limit or existing usage budget before merge.- The current check run has
Backend unit suitefailing. It looks outside this endpoint from the log I inspected, but this PR should either get a green rerun or have maintainers mark the failure as pre-existing before merge.
Given the endpoint exposes sensitive conversation-derived answers and a billable LLM path, this should get human maintainer review for the product/security posture even after those fixes.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
437e64d to
9ecd1d0
Compare
|
Thanks for the review — both points addressed. 1. Rate limit / cost guard. You're right that riding
2. Failing Backend unit suite. It was a stale-base failure outside this endpoint (as your log inspection suggested). Rebased onto latest Agreed this warrants human maintainer review for the product/security posture given the sensitive-data + billable-LLM surface — ready for that look. |
kodjima33
left a comment
There was a problem hiding this comment.
dev-api feature — approved; not auto-merged (feature, outside desktop)
|
Thanks for the follow-up. I re-reviewed the current head ( I’m not dismissing the prior change request yet because this head is still not green: Given this exposes a new public Developer API surface over sensitive conversation data and a billable RAG call path, I’d still keep by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with |
9ecd1d0 to
7fede1a
Compare
|
Thanks — agreed both failures are outside this endpoint, and I checked rather than asserting it. Evidence:
So it isn't a defect in this diff — it was failing against the older base this PR sat on, and
I've now rebased onto latest Agreed on keeping needs-maintainer-review / security-review for the human product/security pass on the sensitive-conversation + billable-RAG surface. |
|
Both are handled in the current tip ( 1. Rate limit (the billable-endpoint concern). 2. Backend unit suite. As you suspected, that's outside this endpoint — it's the pre-existing app-client OpenAPI drift from the memories baseline toggle (#8728), fixed in #10482. Agree it warrants human review for the product/security posture given the sensitive-data + billable-LLM surface — flagging @Git-on-my-level for that. Nothing code-side outstanding that I can see; happy to adjust the cap if 25/hr is the wrong number. |
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks for the quick update here. The dedicated dev:ask rate limit, fail-closed dependency wiring, route-policy entry, and regression test address the original unbounded billable-LLM/cost-guard concern from my first review.
I do need to keep this in changes-requested state on the current head (7fede1a) because the generated API client/spec artifacts are still stale:
Public Developer API contractfails atbackend/scripts/generate_ts_openapi_types.py --check, reporting the four TS generated clients as stale (desktop/windows/.../omiApi.generated.ts,web/app/...,web/admin/..., andweb/personas-open-source/...).Backend unit suitealso fails the generated-client tests:tests/unit/test_app_client_swift_generator.py::test_swift_dto_file_is_generated_from_app_client_openapiandtests/unit/test_app_client_ts_generator.py::test_typescript_schema_types_are_generated_from_app_client_openapi.- The Desktop Swift aggregate is red because the Swift test suite was cancelled; the backend unit output also indicates
desktop/macos/Desktop/Sources/Generated/OmiApi.generated.swiftis out of date and should be regenerated withpython generate_swift_openapi_types.py.
Please regenerate the checked-in TS/Swift API client artifacts from the current OpenAPI output and rerun the contract/unit checks. After that, this still needs human maintainer/security-product sign-off before merge because it adds a public Developer API + CLI surface over sensitive conversation data and a billable cited-RAG path, but I do not see the earlier rate-limit blocker remaining.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
7fede1a to
7112ac8
Compare
|
Regenerated the stale client artifacts — done on the rebased head.
Verified with the .openapi-venv + full pytest venv: That clears the |
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks for continuing to tighten this up. The rate-limit/cost guard and regenerated client artifacts look addressed on the current head, and CI is green now.
I need to request one more change before this is safe for maintainer sign-off: the new ask endpoint hydrates the search hits directly with conversations_db.get_conversations_by_id(...) but does not re-apply the privacy filters that the normal conversation search route applies after hydration.
Concretely, search_conversations(...) defaults include_discarded=True, and /v1/dev/user/ask does not override it, so discarded conversations can still be selected and sent into qa_rag. Also, the public /v1/conversations/search route re-checks not conversation.get('is_locked') after fetching Firestore records because the Typesense index can lag; this new endpoint relies only on the index-side is_locked field, so a stale index can leak locked conversation content into the LLM context and returned answer/sources.
Please mirror the existing search-route hardening here: call search with include_discarded=False, hydrate without photos if possible, and filter hydrated conversations with not conversation.get('is_locked') before building the RAG context/sources. A regression test covering discarded and locked hydrated conversations would be useful because this is a sensitive conversation-data + billable RAG surface.
I’m preserving the human-review/security labels: even after this fix, this should still get human product/security sign-off before merge.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
|
Rebased onto current main. The stale TS/Swift generated-client blocker is now resolved: a maintainer refreshed the generated API artifacts on main ( |
…/user/ask Maintainer privacy review on BasedHardware#10314: the ask endpoint hydrated search hits with get_conversations_by_id but did not re-apply the privacy filters the normal conversation-search route applies after hydration, so sensitive content could reach qa_rag: - search_conversations defaults include_discarded=True and the endpoint did not override it, so a discarded/deleted conversation could be selected. - The Typesense search index's is_locked can lag Firestore, so a stale hit could surface a since-locked conversation; the endpoint trusted the index-side flag only, while /v1/conversations/search re-checks is_locked on the authoritative record after fetch. Pass include_discarded=False, and drop any conversation whose authoritative record is is_locked before it reaches the LLM — mirroring the post-hydration filter in /v1/conversations/search. Verify (.venv): test_dev_ask_endpoint.py 5 passed, incl. two new regressions (discarded excluded from retrieval; a locked conversation from a stale index is dropped). Mutation-checked: reverting either filter fails the matching test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Good catch — fixed. The ask endpoint now applies the same two privacy filters as
Two regression tests added ( |
…/user/ask Maintainer privacy review on BasedHardware#10314: the ask endpoint hydrated search hits with get_conversations_by_id but did not re-apply the privacy filters the normal conversation-search route applies after hydration, so sensitive content could reach qa_rag: - search_conversations defaults include_discarded=True and the endpoint did not override it, so a discarded/deleted conversation could be selected. - The Typesense search index's is_locked can lag Firestore, so a stale hit could surface a since-locked conversation; the endpoint trusted the index-side flag only, while /v1/conversations/search re-checks is_locked on the authoritative record after fetch. Pass include_discarded=False, and drop any conversation whose authoritative record is is_locked before it reaches the LLM — mirroring the post-hydration filter in /v1/conversations/search. Verify (.venv): test_dev_ask_endpoint.py 5 passed, incl. two new regressions (discarded excluded from retrieval; a locked conversation from a stale index is dropped). Mutation-checked: reverting either filter fails the matching test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8232f97 to
4fa14ac
Compare
Answer a natural-language question grounded in the user's own conversations, from the terminal or any Developer-API caller — no desktop app required. Backend: POST /v1/dev/user/ask (conversations:read scope) semantically searches the caller's conversations (search_conversations) and synthesizes a cited answer from the most relevant ones (qa_rag) — the same retrieval + RAG the chat surface uses. Read-only; locked conversations are excluded by the search layer; no LLM call when nothing relevant is found. CLI: `omi ask "<question>"` (with --limit/--timezone/--json), rendering the answer plus its conversation sources. Regenerated the public + app-client OpenAPI contracts and the derived TS clients. Verified: pytest test_dev_ask_endpoint.py (backend, test.sh-isolated) + sdks/python-cli tests/test_ask.py (CLI, real env) pass. Failure-Class: none Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/user/ask Maintainer privacy review on BasedHardware#10314: the ask endpoint hydrated search hits with get_conversations_by_id but did not re-apply the privacy filters the normal conversation-search route applies after hydration, so sensitive content could reach qa_rag: - search_conversations defaults include_discarded=True and the endpoint did not override it, so a discarded/deleted conversation could be selected. - The Typesense search index's is_locked can lag Firestore, so a stale hit could surface a since-locked conversation; the endpoint trusted the index-side flag only, while /v1/conversations/search re-checks is_locked on the authoritative record after fetch. Pass include_discarded=False, and drop any conversation whose authoritative record is is_locked before it reaches the LLM — mirroring the post-hydration filter in /v1/conversations/search. Verify (.venv): test_dev_ask_endpoint.py 5 passed, incl. two new regressions (discarded excluded from retrieval; a locked conversation from a stale index is dropped). Mutation-checked: reverting either filter fails the matching test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4fa14ac to
2938382
Compare
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks for the quick privacy fix here. The current backend code now uses include_discarded=False and re-checks is_locked after hydration before building the RAG context, with regression coverage for both paths, so the data-handling blocker from my last review looks addressed.
I still need to keep this in changes-requested state because the current head is not green: Public Developer API contract is failing on backend/scripts/export_openapi.py --check ../docs/api-reference/openapi.json, reporting that docs/api-reference/openapi.json is stale. This looks likely related to the endpoint docstring/contract text changing with the discarded/locked-conversation hardening while the checked-in public OpenAPI artifact was not regenerated.
Please regenerate the public OpenAPI artifact from the current head and rerun the contract check. After that, I would still keep needs-maintainer-review / security-review for David or another human maintainer to make the final product/security call, because this adds a public Developer API + CLI surface over sensitive conversation data and a billable cited-RAG path.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
What
omi ask "<question>"— ask a natural-language question and get an answer grounded in your own Omi conversations, from the terminal or any Developer-API caller. No desktop app required.The context that lives in your conversations, made queryable where developers actually work — the terminal, scripts, CI — not just the app.
How
POST /v1/dev/user/ask(conversations:readscope). Semantically searches the caller's conversations (search_conversations), then synthesizes a cited answer from the most relevant ones (qa_rag) — the same retrieval + RAG the chat surface already uses. Read-only; locked conversations are excluded by the search layer; no LLM call (no cost) when nothing relevant is found.omi askcommand (--limit,--timezone,--json) that renders the answer plus its conversation sources.Deliberately small: it reuses the existing search + RAG rather than adding new machinery, and grounds on conversations (staying out of the memory-compat rollout path).
Rate limiting / cost guard
Addressing @Git-on-my-level's review — the endpoint invokes an LLM per call, so it now carries a dedicated per-key budget instead of riding the cheap list-read limit:
dev:askpolicy — 25 / hour, per key — inutils/rate_limit_config.py, sized like the billabledev:conversationsLLM-create path and tighter than thedev:conversations_readlist limit (60/hr) the endpoint would otherwise have used.get_uid_with_conversations_read_ask(conversations:readscope +dev:ask), fail-closed on the per-app/key identity via the shared dev-APIcheck_api_key_rate_limit. A leaked or overused key is capped, and a Redis outage denies rather than opens.rate_limitnone→dev:ask/api_key/fail_closed/dependency.Tests
backend/tests/unit/test_dev_ask_endpoint.py— returns a grounded, cited answer from the retrieved conversations; makes no LLM call when nothing relevant matches; and (regression for the review) asserts the endpoint is bound to the rate-limited dependency, which enforces exactlydev:askand is tighter than the list-read limit.sdks/python-cli/tests/test_ask.py— the command POSTs the question and renders the answer + sources;--jsonemits the raw payload.Regenerated the public + app-client OpenAPI contracts and the derived TS + Swift clients for the new endpoint.
Product invariants affected
none
Failure-Class: none